package raft

import (
	"time"
)

//
// example RequestVote RPC arguments structure.
// field names must start with capital letters!
//
type RequestVoteArgs struct {
	Term         int
	CandidateId  int
	LastLogIndex int
	LastLogTerm  int
	IsPreVote    bool
}

//
// example RequestVote RPC reply structure.
// field names must start with capital letters!
//
type RequestVoteReply struct {
	Term        int
	VoteGranted bool
}

func (rf *Raft) setStateByVoteArgs(args *RequestVoteArgs) {
	rf.currentTeam = args.Term
	rf.voteFor = args.CandidateId
	rf.persist(true)
}

//
// example RequestVote RPC handler.
//
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
	DPrintf("[%d] get vote request from %d", rf.me, args.CandidateId)
	reply.VoteGranted = false
	reply.Term = rf.getCurrentTeam()
	if !rf.canVote(args) {
		return
	}

	FPrintf("[%d] argue vote to %d", rf.me, args.CandidateId)
	reply.VoteGranted = true

	if args.IsPreVote {
		return
	}
	rf.updateTimeOut()
	rf.setStateByVoteArgs(args)
	rf.becomeFollower()
}

func (rf *Raft) canVote(args *RequestVoteArgs) bool {
	if args.Term <= rf.currentTeam {
		DPrintf("[%d] can't vote due to term. args.Term: %d, rf.currentTeam: %d", rf.me, args.Term, rf.currentTeam)
		return false
	}
	if lastLog := rf.lastLog(); args.LastLogTerm < lastLog.Term || (args.LastLogTerm == lastLog.Term && args.LastLogIndex < lastLog.Index) {
		DPrintf("[%d] can't vote due to log.", rf.me)
		return false
	}
	return true
}

func (rf *Raft) getCurrentTeam() int {
	return rf.currentTeam
}

func (rf *Raft) setCurrentTeam(term int) {
	if rf.currentTeam == term {
		return
	}
	rf.currentTeam = term
	rf.persist(true)
}

func (rf *Raft) getIdentity() uint8 {
	return rf.identity
}

func (rf *Raft) becomeLeader() {
	//DPrintf("[%d] change identity, now: %d, next: %d", rf.me, rf.identity, identity)
	if rf.identity == Leader {
		return
	}
	rf.identity = Leader
	rf.initLeader()
}

func (rf *Raft) becomeFollower() {
	//DPrintf("[%d] change identity, now: %d, next: %d", rf.me, rf.identity, identity)
	rf.identity = Follower
}

func (rf *Raft) becomeCandidate() {
	//DPrintf("[%d] change identity, now: %d, next: %d", rf.me, rf.identity, identity)
	rf.identity = Candidate
}

// 起一个协程来检查心跳包
func (rf *Raft) waitHeartBeatTimeOut() {
	rf.updateTimeOut()
	for true {
		rf.SleepUntilNextTimeOut()
		identity := rf.getIdentity()
		isTimeOut := Now() >= rf.getNextTimeOut()
		FPrintf("[%d] wake up, identity=%d, isTimeOut=%t, isKilled=%t", rf.me, identity, isTimeOut, rf.killed())
		if rf.killed() || identity != Follower {
			rf.updateTimeOut()
			continue
		}
		if isTimeOut { // follower超时了，这里不可能是candidate
			//if identity == Candidate || (identity == Follower && isTimeOut) { // follower超时了，这里不可能是candidate
			FPrintf("[%d] start leader election", rf.me)
			rf.leaderElection()
			rf.updateTimeOut()
		}
	}
}

func (rf *Raft) leaderElection() {
	// 任期可能会不断增加，要独立出来
	rf.becomeCandidate()
	canElectionTerm := rf.getCurrentTeam() + 1

	// 确认可以参加竞选
	preElectionSuccessChan := make(chan struct{})
	defer close(preElectionSuccessChan)

	// 确认竞选成功
	electionSuccessChan := make(chan struct{})
	defer close(electionSuccessChan)

	// 选举超时；如果是pre vote超时，则发起新的pre vote；如果是vote超时，team+1发起新的vote
	endAt, tick := voteForTimeOutTicker()
	defer tick.Stop()

	preElectionSuccess := false
	go rf.doElection(canElectionTerm, preElectionSuccessChan, endAt, true)
	//DPrintf("[%d] finish preparation before do election, term: %d, endAt: %d", rf.me, canElectionTerm, endAt)
	for true { // 开始选举，要么被送心跳包了，结束；要么成为Leader，结束。
		select {
		case <-preElectionSuccessChan:
			FPrintf("[%d] pre-vote success, term: %d", rf.me, canElectionTerm)
			preElectionSuccess = true
			tick.Stop()
			endAt, tick = voteForTimeOutTicker()
			rf.setStateByVoteArgs(&RequestVoteArgs{CandidateId: rf.me, Term: canElectionTerm})
			go rf.doElection(canElectionTerm, electionSuccessChan, endAt, false)
		case <-electionSuccessChan:
			FPrintf("[%d] !!! vote success, term: %d", rf.me, rf.currentTeam)
			rf.becomeLeader()
			return
		case <-tick.C:
			FPrintf("[%d] vote timeout, pre-vote-success: %t, term: %d", rf.me, preElectionSuccess, rf.currentTeam)
			if rf.getIdentity() != Candidate {
				//	DPrintf("[%d] stop election due to identity change", rf.me)
				return
			} // 收到心跳包，重新成为follower
			tick.Stop()
			endAt, tick = voteForTimeOutTicker()
			if preElectionSuccess {
				canElectionTerm++
				rf.setStateByVoteArgs(&RequestVoteArgs{CandidateId: rf.me, Term: canElectionTerm})
				go rf.doElection(canElectionTerm, electionSuccessChan, endAt, false)
			} else {
				time.Sleep(NextPreVote)
				go rf.doElection(canElectionTerm, preElectionSuccessChan, endAt, true)
			}
		}
	}
}

func (rf *Raft) doElection(term int, c chan struct{}, endAt time.Duration, isPreVote bool) {
	//1. 获得的票数大于半数 -> 进入vote
	//2. 选举超时 -> 稍微等待之后发起新一轮pre-vote
	//3. 收到心跳包 -> 成为follower
	//4. 收到team一样大或者更大RequestVote -> 成为follower
	getSupport := make(chan struct{})
	defer close(getSupport)

	// 给每个人发请求
	lastLog := rf.lastLog()
	for i := range rf.peers {
		if i == rf.me {
			continue
		}
		args := &RequestVoteArgs{term, rf.me, lastLog.Index, lastLog.Term, isPreVote}
		reply := &RequestVoteReply{}
		go func(server int, args *RequestVoteArgs, reply *RequestVoteReply) {
			defer NeverPanic()
			//ok := false
			ok := rf.sendRequestVote(server, args, reply)
			if ok && reply.VoteGranted {
				getSupport <- struct{}{}
			}
		}(i, args, reply)
	}

	tick := time.NewTicker(endAt)
	defer tick.Stop()

	defer NeverPanic()
	count, majority := 1, len(rf.peers)/2+1
	for true {
		//DPrintf("[%d] in election, count: %d, majority: %d, isPreVote: %t", rf.me, count, majority, isPreVote)
		select {
		case <-getSupport:
			count++
			//DPrintf("[%d] get vote support, now: %d, endAt: %d", rf.me, Now(), endAt)
			if count >= majority && Now() < endAt {
				c <- struct{}{}
				return
			}
		case <-tick.C:
			//DPrintf("[%d] max vote for time, goroutine out. term: %d, isPreVote: %t", rf.me, term, isPreVote)
			return
		}
	}
}

func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
	ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
	return ok
}

func voteForTimeOutTicker() (time.Duration, *time.Ticker) {
	endAt := RandTime(VoteForTimeOut, 2*VoteForTimeOut)
	//DPrintf("%s", endAt.String())
	tick := time.NewTicker(endAt)
	return Now() + endAt, tick
}
